#include "st7789.h"
#include "code.h"

#include "w25qxx.h"
#include "menu.h"

#include <stdio.h>
#include <string.h>

const uint32_t hzk16k_base = (24 + 24) * 4096;
const uint32_t hzk24k_base = (24 + 24 + 72) * 4096;

const uint32_t asc16_base  = (24 + 24 + 72 + 160) * 4096;
const uint32_t asc24_base  = (24 + 24 + 72 + 160 + 2) * 4096;

const uint32_t hzk24f_base  = (24 + 24 + 72 + 160 + 2 + 2) * 4096;

static uint8_t  mod, mod_size, line_num;

static int dot;

static int mod_pos;

static int line_mod_pos;

static int font_size = 24;

static uint8_t mod_buf[72];

static uint16_t dot_buffer[24 * 24];

static uint8_t line_mod[2592];  //  max = 54 * 48 = 2592

static uint8_t line_size[54];  //  max = 320 / 12 * 2 = 54

static uint16_t  line_buffer[12][320];  // half a row

int16_t disp = 0;

static uint8_t direction = 0;

static int16_t disp_x, disp_y;

extern int wm8978_volume;


/**
 * @brief Fill the DisplayWindow with single color
 * @param color -> color to Fill with
 * @return none
 */
void Lcd_String(uint16_t x, uint16_t y, char *str, uint8_t size, uint16_t color, uint16_t bgcolor)
{
	uint16_t i, j, tmp;

	uint8_t  qh, wh;
	uint32_t location;

	y = y * size;

	while(*str) {
		/*** HZ ***/
		if(*str > 0xa0) {

			if(x > (ST7789_WIDTH - 24)) break;

			qh = *str++ - 0xa0;
			wh = *str++ - 0xa0;

			if(size == 16)	{
				mod_size = 32;
				location = (94 * (qh - 1) + (wh - 1)) * 32L;
				W25qxx_ReadBytes((uint8_t *)mod_buf, hzk16k_base + location, 32);

			} else if(size == 24)	{
				mod_size = 72;
				location = (94 * (qh - 1) + (wh - 1)) * 72L;
				W25qxx_ReadBytes((uint8_t *)mod_buf, hzk24f_base + location, 72);

			} else return;

			dot = 0;
			for(i = 0; i < mod_size; i++) {
				mod = mod_buf[i];

				for(j = 0; j < 8; j++) {
					if(mod & 0x80)		dot_buffer[dot++] = color;
					else 				dot_buffer[dot++] = bgcolor;
					mod <<= 1;
				}
			}

			ST7789_SetAddressWindow(x, y, x + size - 1, y + size - 1);
			ST7789_WriteData((uint8_t *)dot_buffer, dot * 2);

			x += size;
		/*** ASC II ***/
		} else {

			if(x > (ST7789_WIDTH - 12)) break;

			// csize = (size / 8 + ((size % 8) ? 1 : 0)) * (size / 2);

			if(size == 16)	{
				mod_size = 16;
				location = (*str++ - 0) * 16L;
				W25qxx_ReadBytes((uint8_t *)mod_buf, asc16_base + location, 16);

			} else if(size == 24)	{
				mod_size = 48;
				location = (*str++ - 32) * 48L;
				W25qxx_ReadBytes((uint8_t *)mod_buf, asc24_base + location, 48);

			} else return;

			dot = 0;
			for(i = 0; i < mod_size; i++) {
				mod = mod_buf[i];

				if((size == 24) & (i % 2))	tmp = 4;
				else						tmp = 8;

				for(j = 0; j < tmp; j++) {
					if(mod & 0x80)		dot_buffer[dot++] = color;
					else				dot_buffer[dot++] = bgcolor;
					mod <<= 1;
				}
			}

			ST7789_SetAddressWindow(x, y, x + size / 2 - 1, y + size - 1);
			ST7789_WriteData((uint8_t *)dot_buffer, dot * 2);

			x += (size / 2);
		}
	}

	if(x < ST7789_WIDTH) {
		dot = 0;
		for(i = 0; i < 24 * 12; i++) {
			dot_buffer[dot++] = bgcolor;
		}

		ST7789_SetAddressWindow(x, y, ST7789_WIDTH - 1, y + size - 1);

		tmp = ST7789_WIDTH - x;
		for(i = 0; i < tmp; ) {
			ST7789_WriteData((uint8_t *)dot_buffer, 24 * 24);
			i += 12;
		}
	}
}

/**
 * @brief Fill the DisplayWindow with single color
 * @param color -> color to Fill with
 * @return none
 */
static void build_line_mod(char *str)
{
	uint8_t  qh, wh;

	line_mod_pos = 0;
	line_num = 0;

	while(*str) {
		/*** HZ ***/
		if(*str > 0xa0) {
			qh = *str++ - 0xa0;
			wh = *str++ - 0xa0;

			if(font_size == 16)	{
				mod_size = 32;
				W25qxx_ReadBytes((uint8_t *)(line_mod + line_mod_pos), hzk16k_base + (94 * (qh - 1) + (wh - 1)) * 32L, mod_size);

			} else if(font_size == 24)	{
				mod_size = 72;
				W25qxx_ReadBytes((uint8_t *)(line_mod + line_mod_pos), hzk24f_base + (94 * (qh - 1) + (wh - 1)) * 72L, mod_size);

			} else return;
		/*** ASC II ***/
		} else {
			if(font_size == 16)	{
				mod_size = 16;
				W25qxx_ReadBytes((uint8_t *)(line_mod + line_mod_pos), asc16_base + (*str++ - 0) * 16L, mod_size);

			} else if(font_size == 24)	{
				mod_size = 48;
				W25qxx_ReadBytes((uint8_t *)(line_mod + line_mod_pos), asc24_base + (*str++ - 32) * 48L, mod_size);

			} else return;
		}
		line_size[line_num++] = mod_size;
		line_mod_pos += mod_size;
	}
}

/**
 * @brief Fill the DisplayWindow with single color
 * @param color -> color to Fill with
 * @return none
 */
static void disp_line(uint8_t height)
{
	uint16_t i, j, k, tmp;

	uint8_t pos_x, pos_y;

	int x, y;

	int skip, skip_counter;

	line_mod_pos = 0;

	k = 0;
	x = disp_x;
	
	tmp = disp_x;

	if(disp > 0) {

		for(;;) {

			mod_size = line_size[k];

			if(mod_size == 72) {
				if(tmp < 24) {
					x = 24 - tmp;
					break;
				}
				tmp = tmp - 24;
			} else {
				if(tmp < 12) {
					x = 12 - tmp;
					break;
				}
				tmp = tmp - 12;
			}
			k++;
			line_mod_pos += mod_size;
		}

		skip = tmp;
		skip_counter = skip;

		/*** HZ ***/
		if(mod_size == 72) {

			mod_size >>= 1;

			pos_x = 0;
			pos_y = 0;

			if(height)  line_mod_pos += mod_size;

			for(i = 0; i < mod_size; i++) {
				mod = line_mod[line_mod_pos++];

				for(j = 0; j < 8; j++) {
					if(skip_counter) {
						mod <<= 1;
						skip_counter--;
						continue;
					}
					if(mod & 0x80)		line_buffer[pos_y][pos_x++] = 0x20fd;
					else 				line_buffer[pos_y][pos_x++] = 0x0000;
					mod <<= 1;
				}
				if(pos_x >= x) {
					pos_x = 0;
					pos_y++;
					skip_counter = skip;
				}
			}
			if(height == 0)  line_mod_pos += mod_size;

		/*** ASC II ***/
		} else {

			mod_size >>= 1;

			pos_x = 0;
			pos_y = 0;

			if(height)  line_mod_pos += mod_size;

			for(i = 0; i < mod_size; i++) {
				mod = line_mod[line_mod_pos++];

				if((font_size == 24) && (i % 2))	tmp = 4;
				else						tmp = 8;

				for(j = 0; j < tmp; j++) {
					if(skip_counter) {
						mod <<= 1;
						skip_counter--;
						continue;
					}

					if(mod & 0x80)		line_buffer[pos_y][pos_x++] = 0x20fd;
					else				line_buffer[pos_y][pos_x++] = 0x0000;

					mod <<= 1;
				}
				if(pos_x >= x) {
					pos_x = 0;
					pos_y++;
					skip_counter = skip;
				}
			}
			if(height == 0) line_mod_pos += mod_size;
		}
		// ship to next char !!!
		k++;
	}

	// ---------------------------------------------------------------------------
	for(; k < line_num; k++) {

		mod_size = line_size[k];

		/*** HZ ***/
		if(mod_size == 72) {

			mod_size >>= 1;

			pos_x = 0;
			pos_y = 0;

			if(height)  line_mod_pos += mod_size;

			for(i = 0; i < mod_size; i++) {
				mod = line_mod[line_mod_pos++];

				for(j = 0; j < 8; j++) {
					if((x + pos_x) < ST7789_WIDTH) {
					if(mod & 0x80)		line_buffer[pos_y][x + pos_x++] = 0x20fd;
					else 				line_buffer[pos_y][x + pos_x++] = 0x0000;
					} else {
						pos_x++;
					}
					mod <<= 1;
						
				}
				if(pos_x >= font_size) {
					pos_x = 0;
					pos_y++;
				}
			}
			x += font_size;

			if(height == 0)  line_mod_pos += mod_size;

		/*** ASC II ***/
		} else {

			mod_size >>= 1;

			pos_x = 0;
			pos_y = 0;

			if(height)  line_mod_pos += mod_size;

			for(i = 0; i < mod_size; i++) {
				mod = line_mod[line_mod_pos++];

				if((font_size == 24) && (i % 2))	tmp = 4;
				else						tmp = 8;

				for(j = 0; j < tmp; j++) {
					if((x + pos_x) < ST7789_WIDTH) {
									
					if(mod & 0x80)		line_buffer[pos_y][x + pos_x++] = 0x20fd;
					else				line_buffer[pos_y][x + pos_x++] = 0x0000;
					} else {
						pos_x++;
					}
					mod <<= 1;
					
				}
				if(pos_x >= font_size / 2) {
					pos_x = 0;
					pos_y++;
				}
			}
			x += (font_size / 2);

			if(height == 0) line_mod_pos += mod_size;
		}
	}
	// ------------------------------------------------------------------------------

	y = 8 * 24 + height;

	// printf("disp_x = %d  y = %d\r\n", disp_x, y);
	ST7789_SetAddressWindow(0, y, ST7789_WIDTH  - 1, y + 12 - 1);
	ST7789_WriteData((uint8_t *)line_buffer, ST7789_WIDTH * 24);
}

/**
  * @brief  The application .
  * @retval void
  */
void build_ooo(void)
{
	uint16_t  length;

	disp_x = 0;
	direction = 0;

	length = strlen(dir_list[dir_index].dirname) * font_size / 2;

	if(length > ST7789_WIDTH * 2) {
		disp = 0;
		return;
	}

	disp = length - ST7789_WIDTH;

	build_line_mod(dir_list[dir_index].dirname);

	memset(line_buffer, 0x00, sizeof(line_buffer));
	
	disp_line(0);
	disp_line(12);

}

/**
  * @brief  The application .
  * @retval void
  */
void disp_ooo(void)
{
	if(disp > 0) {
		if(direction == 0) {
			disp_x++;
			if(disp_x == disp) {
				direction = 1;
			}
		} else {
			disp_x--;
			if(disp_x == 0) {
				direction = 0;
			}
		}
		// Lcd_String(0, 8, dir_list[dir_index].dirname, 24, 0x20fd, 0x0000);
		disp_line(0);
		disp_line(12);

	} else {

		if(direction == 0) {
			disp_x++;
			if(disp_x == (-disp)) {
				direction = 1;
			}
		} else {
			disp_x--;
			if(disp_x == 0) {
				direction = 0;
			}
		}
		
		disp_line(0);
		disp_line(12);
	}
}

/**
  * @brief  The application .
  * @retval void
  */
void dis_start(void)
{
	uint8_t  i, j, tmp;

	uint8_t data_color[] = {GREEN >> 8, GREEN & 0xFF};
	uint8_t data_bgcolor[] = {BLACK >> 8, BLACK & 0xFF};

	ST7789_SetAddressWindow(136, 9 * 24, 136 + 48 - 1,  10 * 24 - 1);

	for(i = 0; i < 144; i++) {
		mod = start[i];
		for(j = 0; j < 8; j++) {
			if(mod & 0x80)		ST7789_WriteData(data_color, sizeof(data_color));
			else				ST7789_WriteData(data_bgcolor, sizeof(data_bgcolor));
			mod <<= 1;
		}
	}

	// disp(:::::)
	ST7789_SetAddressWindow(52, 9 * 24, 52 + 12 - 1, 10 * 24 - 1);
	ST7789_WriteData((uint8_t *)mod_number[10], sizeof(mod_number[10]));
}

/**
  * @brief  The application .
  * @retval void
  */
void dis_stop(void)
{
	uint8_t  i, j, mod;

	uint8_t data_color[] = {GREEN >> 8, GREEN & 0xFF};
	uint8_t data_bgcolor[] = {BLACK >> 8, BLACK & 0xFF};

	ST7789_SetAddressWindow(136, 9 * 24, 136 + 48 - 1,  10 * 24 - 1);

	for(i = 0; i < 144; i++) {
		mod = stop[i];
		for(j = 0; j < 8; j++) {
			if(mod & 0x80)		ST7789_WriteData(data_color, sizeof(data_color));
			else				ST7789_WriteData(data_bgcolor, sizeof(data_bgcolor));
			mod <<= 1;
		}
	}
}

/**
  * @brief  The application .
  * @retval void
  */
void dis_pause(void)
{
	uint8_t  i, j, mod;

	uint8_t data_color[] = {GREEN >> 8, GREEN & 0xFF};
	uint8_t data_bgcolor[] = {BLACK >> 8, BLACK & 0xFF};

	ST7789_SetAddressWindow(136, 9 * 24, 136 + 48 - 1,  10 * 24 - 1);

	for(i = 0; i < 144; i++) {
		mod = pause[i];
		for(j = 0; j < 8; j++) {
			if(mod & 0x80)		ST7789_WriteData(data_color, sizeof(data_color));
			else				ST7789_WriteData(data_bgcolor, sizeof(data_bgcolor));
			mod <<= 1;
		}
	}
}

/**
  * @brief  The application .
  * @retval void
  */
void dis_sec(uint8_t sec)
{
	uint8_t 	hi, lo;

	hi = sec / 10;
	lo = sec % 10;

	ST7789_SetAddressWindow(70, 9 * 24, 70 + 12 - 1, 10 * 24 - 1);
	ST7789_WriteData((uint8_t *)mod_number[hi], sizeof(mod_number[hi]));

	ST7789_SetAddressWindow(86, 9 * 24, 86 + 12 - 1, 10 * 24 - 1);
	ST7789_WriteData((uint8_t *)mod_number[lo], sizeof(mod_number[lo]));

}

/**
  * @brief  The application .
  * @retval void
  */
void dis_min(uint8_t min)
{
	uint8_t 	hi, lo;

	hi = min / 10;
	lo = min % 10;

	ST7789_SetAddressWindow(20, 9 * 24, 20 + 12 - 1, 10 * 24 - 1);
	ST7789_WriteData((uint8_t *)mod_number[hi], sizeof(mod_number[hi]));

	ST7789_SetAddressWindow(36, 9 * 24, 36 + 12 - 1, 10 * 24 - 1);
	ST7789_WriteData((uint8_t *)mod_number[lo], sizeof(mod_number[lo]));

}

/**
  * @brief  The application .
  * @retval void
  */
void dis_vol(void)
{
	uint8_t 	hi, lo;

	hi = wm8978_volume / 10;
	lo = wm8978_volume % 10;


	ST7789_SetAddressWindow(230, 9 * 24, 230 + 12 - 1, 10 * 24 - 1);	//	150, 216, 12, 24);
	ST7789_WriteData((uint8_t *)mod_number[hi], sizeof(mod_number[hi]));

	ST7789_SetAddressWindow(246, 9 * 24, 246 + 12 - 1, 10 * 24 - 1);	//	166, 216, 12, 24);
	ST7789_WriteData((uint8_t *)mod_number[lo], sizeof(mod_number[lo]));
}

/**
  * @brief  The application .
  * @retval void
  */
void dis_char(uint8_t ch)
{
	// char 0 = 48

	uint8_t  i, j, mod;
	uint16_t	tmp;

	W25qxx_ReadBytes((uint8_t *)mod_buf, asc24_base + (ch - 32) * 48L, 48);

	// LCD_Set_Window(132, 10 * 24, FONT_SIZE / 2, FONT_SIZE);
	// LCD_WriteRAM_Prepare();

	printf("\r\n\n");

	for(i = 0; i < 48; i++) {
		mod = mod_buf[i];

		if(i % 2)	tmp = 4;
		else	tmp = 8;

		for(j = 0; j < tmp; j++) {
			if(mod & 0x80)		printf("0x07e0,");  //  LCD_RAM = GREEN;
			else				printf("0x0000,");  //  LCD_RAM = BLACK;
			mod <<= 1;
		}
	}

	printf("\r\n\n");
}

/**
 * @brief Fill the DisplayWindow with single color
 * @param color -> color to Fill with
 * @return none
 */
void ST7789_Fill_Color(uint16_t color)
{
	uint16_t i, j;
	uint8_t data[] = {color >> 8, color & 0xFF};

	uint16_t buf[ST7789_WIDTH];

	j = (color >> 8) | ((color & 0xFF) << 8);

	for(i = 0; i < ST7789_WIDTH; i++)
		buf[i] = j;

	ST7789_SetAddressWindow(0, 0, ST7789_WIDTH - 1, ST7789_HEIGHT - 1);

	ST7789_Select();

#if 0
	for (i = 0; i < ST7789_WIDTH; i++)
		for (j = 0; j < ST7789_HEIGHT; j++) {
			ST7789_WriteData(data, sizeof(data));
		}
#else
	ST7789_DC_Set();
	for (i = 0; i < ST7789_HEIGHT; i++)
		HAL_SPI_Transmit(&ST7789_SPI_PORT, (uint8_t *)buf, ST7789_WIDTH * 2, HAL_MAX_DELAY);
#endif

	ST7789_UnSelect();
}

/**
 * @brief Draw a Pixel
 * @param x&y -> coordinate to Draw
 * @param color -> color of the Pixel
 * @return none
 */
void ST7789_DrawPixel(uint16_t x, uint16_t y, uint16_t color)
{
	if ((x >= ST7789_WIDTH) ||
	    (y >= ST7789_HEIGHT))	return;

	ST7789_SetAddressWindow(x, y, x, y);
	uint8_t data[] = {color >> 8, color & 0xFF};
	ST7789_Select();
	ST7789_WriteData(data, sizeof(data));
	ST7789_UnSelect();
}

/**
 * @brief Fill an Area with single color
 * @param xSta&ySta -> coordinate of the start point
 * @param xEnd&yEnd -> coordinate of the end point
 * @param color -> color to Fill with
 * @return none
 */
void ST7789_Fill(uint16_t xSta, uint16_t ySta, uint16_t xEnd, uint16_t yEnd, uint16_t color)
{
	if ((xEnd >= ST7789_WIDTH) ||
	    (yEnd >= ST7789_HEIGHT))	return;
	ST7789_Select();
	uint16_t i, j;
	ST7789_SetAddressWindow(xSta, ySta, xEnd, yEnd);
	for (i = ySta; i <= yEnd; i++)
		for (j = xSta; j <= xEnd; j++) {
			uint8_t data[] = {color >> 8, color & 0xFF};
			ST7789_WriteData(data, sizeof(data));
		}
	ST7789_UnSelect();
}

/**
 * @brief Draw a big Pixel at a point
 * @param x&y -> coordinate of the point
 * @param color -> color of the Pixel
 * @return none
 */
void ST7789_DrawPixel_4px(uint16_t x, uint16_t y, uint16_t color)
{
	if ((x <= 0) || (x > ST7789_WIDTH) ||
	    (y <= 0) || (y > ST7789_HEIGHT))	return;
	ST7789_Select();
	ST7789_Fill(x - 1, y - 1, x + 1, y + 1, color);
	ST7789_UnSelect();
}

/**
 * @brief Draw a line with single color
 * @param x1&y1 -> coordinate of the start point
 * @param x2&y2 -> coordinate of the end point
 * @param color -> color of the line to Draw
 * @return none
 */
void ST7789_DrawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1,
                     uint16_t color)
{
	uint16_t swap;
	uint16_t steep = ABS(y1 - y0) > ABS(x1 - x0);
	if (steep) {
		swap = x0;
		x0 = y0;
		y0 = swap;

		swap = x1;
		x1 = y1;
		y1 = swap;
		//_swap_int16_t(x0, y0);
		//_swap_int16_t(x1, y1);
	}

	if (x0 > x1) {
		swap = x0;
		x0 = x1;
		x1 = swap;

		swap = y0;
		y0 = y1;
		y1 = swap;
		//_swap_int16_t(x0, x1);
		//_swap_int16_t(y0, y1);
	}

	int16_t dx, dy;
	dx = x1 - x0;
	dy = ABS(y1 - y0);

	int16_t err = dx / 2;
	int16_t ystep;

	if (y0 < y1) {
		ystep = 1;
	} else {
		ystep = -1;
	}

	for (; x0 <= x1; x0++) {
		if (steep) {
			ST7789_DrawPixel(y0, x0, color);
		} else {
			ST7789_DrawPixel(x0, y0, color);
		}
		err -= dy;
		if (err < 0) {
			y0 += ystep;
			err += dx;
		}
	}
}

/**
 * @brief Draw a Rectangle with single color
 * @param xi&yi -> 2 coordinates of 2 top points.
 * @param color -> color of the Rectangle line
 * @return none
 */
void ST7789_DrawRectangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color)
{
	ST7789_Select();
	ST7789_DrawLine(x1, y1, x2, y1, color);
	ST7789_DrawLine(x1, y1, x1, y2, color);
	ST7789_DrawLine(x1, y2, x2, y2, color);
	ST7789_DrawLine(x2, y1, x2, y2, color);
	ST7789_UnSelect();
}

/**
 * @brief Draw a circle with single color
 * @param x0&y0 -> coordinate of circle center
 * @param r -> radius of circle
 * @param color -> color of circle line
 * @return  none
 */
void ST7789_DrawCircle(uint16_t x0, uint16_t y0, uint8_t r, uint16_t color)
{
	int16_t f = 1 - r;
	int16_t ddF_x = 1;
	int16_t ddF_y = -2 * r;
	int16_t x = 0;
	int16_t y = r;

	ST7789_Select();
	ST7789_DrawPixel(x0, y0 + r, color);
	ST7789_DrawPixel(x0, y0 - r, color);
	ST7789_DrawPixel(x0 + r, y0, color);
	ST7789_DrawPixel(x0 - r, y0, color);

	while (x < y) {
		if (f >= 0) {
			y--;
			ddF_y += 2;
			f += ddF_y;
		}
		x++;
		ddF_x += 2;
		f += ddF_x;

		ST7789_DrawPixel(x0 + x, y0 + y, color);
		ST7789_DrawPixel(x0 - x, y0 + y, color);
		ST7789_DrawPixel(x0 + x, y0 - y, color);
		ST7789_DrawPixel(x0 - x, y0 - y, color);

		ST7789_DrawPixel(x0 + y, y0 + x, color);
		ST7789_DrawPixel(x0 - y, y0 + x, color);
		ST7789_DrawPixel(x0 + y, y0 - x, color);
		ST7789_DrawPixel(x0 - y, y0 - x, color);
	}
	ST7789_UnSelect();
}

/**
 * @brief Draw an Image on the screen
 * @param x&y -> start point of the Image
 * @param w&h -> width & height of the Image to Draw
 * @param data -> pointer of the Image array
 * @return none
 */
void ST7789_DrawImage(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint16_t *data)
{
	if ((x >= ST7789_WIDTH) || (y >= ST7789_HEIGHT))
		return;
	if ((x + w - 1) >= ST7789_WIDTH)
		return;
	if ((y + h - 1) >= ST7789_HEIGHT)
		return;

	ST7789_Select();
	ST7789_SetAddressWindow(x, y, x + w - 1, y + h - 1);
	ST7789_WriteData((uint8_t *)data, sizeof(uint16_t) * w * h);
	ST7789_UnSelect();
}

/**
 * @brief Invert Fullscreen color
 * @param invert -> Whether to invert
 * @return none
 */
void ST7789_InvertColors(uint8_t invert)
{
	ST7789_Select();
	ST7789_WriteCommand(invert ? 0x21 /* INVON */ : 0x20 /* INVOFF */);
	ST7789_UnSelect();
}

/**
 * @brief Write a char
 * @param  x&y -> cursor of the start point.
 * @param ch -> char to write
 * @param font -> fontstyle of the string
 * @param color -> color of the char
 * @param bgcolor -> background color of the char
 * @return  none
 */
void ST7789_WriteChar(uint16_t x, uint16_t y, char ch, FontDef font, uint16_t color, uint16_t bgcolor)
{
	uint32_t i, b, j;
	ST7789_Select();
	ST7789_SetAddressWindow(x, y, x + font.width - 1, y + font.height - 1);

	for (i = 0; i < font.height; i++) {
		b = font.data[(ch - 32) * font.height + i];
		for (j = 0; j < font.width; j++) {
			if ((b << j) & 0x8000) {
				uint8_t data[] = {color >> 8, color & 0xFF};
				ST7789_WriteData(data, sizeof(data));
			} else {
				uint8_t data[] = {bgcolor >> 8, bgcolor & 0xFF};
				ST7789_WriteData(data, sizeof(data));
			}
		}
	}
	ST7789_UnSelect();
}

/**
 * @brief Write a string
 * @param  x&y -> cursor of the start point.
 * @param str -> string to write
 * @param font -> fontstyle of the string
 * @param color -> color of the string
 * @param bgcolor -> background color of the string
 * @return  none
 */
void ST7789_WriteString(uint16_t x, uint16_t y, const char *str, FontDef font, uint16_t color, uint16_t bgcolor)
{
	ST7789_Select();
	while (*str) {
		if (x + font.width >= ST7789_WIDTH) {
			x = 0;
			y += font.height;
			if (y + font.height >= ST7789_HEIGHT) {
				break;
			}

			if (*str == ' ') {
				// skip spaces in the beginning of the new line
				str++;
				continue;
			}
		}
		ST7789_WriteChar(x, y, *str, font, color, bgcolor);
		x += font.width;
		str++;
	}
	ST7789_UnSelect();
}

/**
 * @brief Draw a filled Rectangle with single color
 * @param  x&y -> coordinates of the starting point
 * @param w&h -> width & height of the Rectangle
 * @param color -> color of the Rectangle
 * @return  none
 */
void ST7789_DrawFilledRectangle(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color)
{
	ST7789_Select();
	uint8_t i;

	/* Check input parameters */
	if (x >= ST7789_WIDTH ||
	    y >= ST7789_HEIGHT) {
		/* Return error */
		return;
	}

	/* Check width and height */
	if ((x + w) >= ST7789_WIDTH) {
		w = ST7789_WIDTH - x;
	}
	if ((y + h) >= ST7789_HEIGHT) {
		h = ST7789_HEIGHT - y;
	}

	/* Draw lines */
	for (i = 0; i <= h; i++) {
		/* Draw lines */
		ST7789_DrawLine(x, y + i, x + w, y + i, color);
	}
	ST7789_UnSelect();
}

/**
 * @brief Draw a Triangle with single color
 * @param  xi&yi -> 3 coordinates of 3 top points.
 * @param color ->color of the lines
 * @return  none
 */
void ST7789_DrawTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3, uint16_t color)
{
	ST7789_Select();
	/* Draw lines */
	ST7789_DrawLine(x1, y1, x2, y2, color);
	ST7789_DrawLine(x2, y2, x3, y3, color);
	ST7789_DrawLine(x3, y3, x1, y1, color);
	ST7789_UnSelect();
}

/**
 * @brief Draw a filled Triangle with single color
 * @param  xi&yi -> 3 coordinates of 3 top points.
 * @param color ->color of the triangle
 * @return  none
 */
void ST7789_DrawFilledTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3, uint16_t color)
{
	ST7789_Select();
	int16_t deltax = 0, deltay = 0, x = 0, y = 0, xinc1 = 0, xinc2 = 0,
	        yinc1 = 0, yinc2 = 0, den = 0, num = 0, numadd = 0, numpixels = 0,
	        curpixel = 0;

	deltax = ABS(x2 - x1);
	deltay = ABS(y2 - y1);
	x = x1;
	y = y1;

	if (x2 >= x1) {
		xinc1 = 1;
		xinc2 = 1;
	} else {
		xinc1 = -1;
		xinc2 = -1;
	}

	if (y2 >= y1) {
		yinc1 = 1;
		yinc2 = 1;
	} else {
		yinc1 = -1;
		yinc2 = -1;
	}

	if (deltax >= deltay) {
		xinc1 = 0;
		yinc2 = 0;
		den = deltax;
		num = deltax / 2;
		numadd = deltay;
		numpixels = deltax;
	} else {
		xinc2 = 0;
		yinc1 = 0;
		den = deltay;
		num = deltay / 2;
		numadd = deltax;
		numpixels = deltay;
	}

	for (curpixel = 0; curpixel <= numpixels; curpixel++) {
		ST7789_DrawLine(x, y, x3, y3, color);

		num += numadd;
		if (num >= den) {
			num -= den;
			x += xinc1;
			y += yinc1;
		}
		x += xinc2;
		y += yinc2;
	}
	ST7789_UnSelect();
}

/**
 * @brief Draw a Filled circle with single color
 * @param x0&y0 -> coordinate of circle center
 * @param r -> radius of circle
 * @param color -> color of circle
 * @return  none
 */
void ST7789_DrawFilledCircle(int16_t x0, int16_t y0, int16_t r, uint16_t color)
{
	ST7789_Select();
	int16_t f = 1 - r;
	int16_t ddF_x = 1;
	int16_t ddF_y = -2 * r;
	int16_t x = 0;
	int16_t y = r;

	ST7789_DrawPixel(x0, y0 + r, color);
	ST7789_DrawPixel(x0, y0 - r, color);
	ST7789_DrawPixel(x0 + r, y0, color);
	ST7789_DrawPixel(x0 - r, y0, color);
	ST7789_DrawLine(x0 - r, y0, x0 + r, y0, color);

	while (x < y) {
		if (f >= 0) {
			y--;
			ddF_y += 2;
			f += ddF_y;
		}
		x++;
		ddF_x += 2;
		f += ddF_x;

		ST7789_DrawLine(x0 - x, y0 + y, x0 + x, y0 + y, color);
		ST7789_DrawLine(x0 + x, y0 - y, x0 - x, y0 - y, color);

		ST7789_DrawLine(x0 + y, y0 + x, x0 - y, y0 + x, color);
		ST7789_DrawLine(x0 + y, y0 - x, x0 - y, y0 - x, color);
	}
	ST7789_UnSelect();
}


/**
 * @brief Open/Close tearing effect line
 * @param tear -> Whether to tear
 * @return none
 */
void ST7789_TearEffect(uint8_t tear)
{
	ST7789_Select();
	ST7789_WriteCommand(tear ? 0x35 /* TEON */ : 0x34 /* TEOFF */);
	ST7789_UnSelect();
}


/**
 * @brief A Simple test function for ST7789
 * @param  none
 * @return  none
 */
void ST7789_Test(void)
{
	ST7789_Fill_Color(WHITE);
	HAL_Delay(1000);
	ST7789_WriteString(10, 20, "Speed Test", Font_11x18, RED, WHITE);
	HAL_Delay(1000);
	ST7789_Fill_Color(CYAN);
	ST7789_Fill_Color(RED);
	ST7789_Fill_Color(BLUE);
	ST7789_Fill_Color(GREEN);
	ST7789_Fill_Color(YELLOW);
	ST7789_Fill_Color(BROWN);
	ST7789_Fill_Color(DARKBLUE);
	ST7789_Fill_Color(MAGENTA);
	ST7789_Fill_Color(LIGHTGREEN);
	ST7789_Fill_Color(LGRAY);
	ST7789_Fill_Color(LBBLUE);
	ST7789_Fill_Color(WHITE);
	HAL_Delay(500);

	ST7789_WriteString(10, 10, "Font test.", Font_16x26, GBLUE, WHITE);
	ST7789_WriteString(10, 50, "Hello Steve!", Font_7x10, RED, WHITE);
	ST7789_WriteString(10, 75, "Hello Steve!", Font_11x18, YELLOW, WHITE);
	ST7789_WriteString(10, 100, "Hello Steve!", Font_16x26, MAGENTA, WHITE);
	HAL_Delay(1000);

	ST7789_Fill_Color(RED);
	ST7789_WriteString(10, 10, "Rect./Line.", Font_11x18, YELLOW, RED);
	ST7789_DrawRectangle(30, 30, 100, 100, WHITE);
	HAL_Delay(1000);

	ST7789_Fill_Color(RED);
	ST7789_WriteString(10, 10, "Filled Rect.", Font_11x18, YELLOW, RED);
	ST7789_DrawFilledRectangle(30, 30, 50, 50, WHITE);
	HAL_Delay(1000);


	ST7789_Fill_Color(RED);
	ST7789_WriteString(10, 10, "Circle.", Font_11x18, YELLOW, RED);
	ST7789_DrawCircle(60, 60, 25, WHITE);
	HAL_Delay(1000);

	ST7789_Fill_Color(RED);
	ST7789_WriteString(10, 10, "Filled Cir.", Font_11x18, YELLOW, RED);
	ST7789_DrawFilledCircle(60, 60, 25, WHITE);
	HAL_Delay(1000);

	ST7789_Fill_Color(RED);
	ST7789_WriteString(10, 10, "Triangle", Font_11x18, YELLOW, RED);
	ST7789_DrawTriangle(30, 30, 30, 70, 60, 40, WHITE);
	HAL_Delay(1000);

	ST7789_Fill_Color(RED);
	ST7789_WriteString(10, 10, "Filled Tri", Font_11x18, YELLOW, RED);
	ST7789_DrawFilledTriangle(30, 30, 30, 70, 60, 40, WHITE);
	HAL_Delay(1000);

	//	If FLASH cannot storage anymore datas, please delete codes below.
	ST7789_Fill_Color(WHITE);
	ST7789_DrawImage(0, 0, 128, 128, (uint16_t *)saber);
	HAL_Delay(3000);
}


